package com.atguigu.tingshu.album.service.impl;

import com.atguigu.tingshu.album.service.TestService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
@Service
public class TestServiceImpl implements TestService {

    @Autowired
    private RedisTemplate<String , String> redisTemplate ;

    @Autowired
    private RedissonClient redissonClient ;

    // private Lock lock = new ReentrantLock() ;



    @SneakyThrows
    @Override
    public void testLock() {

        /**
         * Redisson中的分布式锁进行实现的时候使用到的是Redis中的hash结构。
         */

        // 加锁
        RLock rLock = redissonClient.getLock("lock");
        // rLock.lock();           // 获取锁的方法，如果线程没有获取到锁，那么此时就处于阻塞状态 , 存在默认的过期时间(30s)    存在续期机制
        // rLock.lock(60 , TimeUnit.SECONDS);           // 没有续期机制

        boolean tryLock = rLock.tryLock();              // tryLock方法是尝试获取锁，如果该线程获取到了锁，返回的就是true，如果该线程没有获取到锁返回就是false
                                                        // 不会阻塞未获取到锁的线程，锁也存在默认的过期时间(30s) , 会存在续期机制
        // boolean tryLock = rLock.tryLock(5, TimeUnit.SECONDS);       // 第一个参数表示的是获取锁的最大等待时间
        // boolean tryLock = rLock.tryLock(5, 30, TimeUnit.SECONDS);    // 显式的指定了锁的过期时间，那么此时就没有续期机制

        if(tryLock) {

            log.info(Thread.currentThread().getName() + "获取到了锁，开始执行业务代码....");

            try {

                // 执行业务代码
                int a = 1 / 0;

                /**
                 * 为什么执行业务主线程产生异常了，锁的续期机制不受影响？ 原因是因为续期机制使用到了新的线程 + 定时任务(10s)
                 * 面试题：在实际开发中有没有遇到过来一些问题，并且如何进行解决的？
                 */

            }catch (Exception e) {
                e.printStackTrace();
            } finally {

                // 释放锁
                log.info(Thread.currentThread().getName() + "业务代码执行完毕了，释放了分布式锁...");
                rLock.unlock();

            }

        }else {
            log.info(Thread.currentThread().getName() + "没有获取到分布式锁...");
        }


    }

    // 解锁方法
    public void unlock(String uuid) {
        String value = redisTemplate.opsForValue().get("lock");         // 9.5s
//        if(value.equals(uuid)) {
//            redisTemplate.delete("lock") ;   // del   -------> 0.5  -----> 0.3   ----> redis
//            log.info("锁被释放了...");
//        }else {
//            log.info("锁是别的线程加的，释放锁失败了...");
//        }

        // 在释放锁的时候就需要保证判断操作和删除操作的原子性
        String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] \n" +
                "then\n" +
                "    return redis.call(\"del\" , KEYS[1]) \n" +
                "else\n" +
                "    return 0\n" +
                "end" ;

        Long result = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList("lock"), uuid);
        System.out.println("lua脚本的执行结果为：" + result);
    }

    // 加锁方法
    public void lock(String uuid) {

        // 调用redis的setnx命令，向Redis中插入数据
        while(!redisTemplate.opsForValue().setIfAbsent("lock", uuid , 30 , TimeUnit.SECONDS)) {}
        // redisTemplate.expire("lock" , 30 , TimeUnit.SECONDS) ;

        /**
         * 问题：执行完加锁代码以后，在执行给锁设置过期时间之前服务器当街了，导致出现死锁现象。针对这个问题的解决方法：保证加速和给锁设置过期时间的原子性。
         */

        // 通过定时任务给锁进行续期
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);
        scheduledThreadPool.scheduleAtFixedRate(() -> {
            redisTemplate.expire("lock" , 30 , TimeUnit.SECONDS) ;
        } , 10 , 10 , TimeUnit.SECONDS) ;

    }

    public void incr() {

//        try {
//            TimeUnit.SECONDS.sleep(60);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }

        //1.从Redis缓存中获取key="num"的值
        String value = redisTemplate.opsForValue().get("num");
        if (StringUtils.isEmpty(value)) {
            redisTemplate.opsForValue().set("num" , "1");
        }else {

            //2.对获取到值进行+1操作
            int num = Integer.parseInt(value);
            ++num ;
            redisTemplate.opsForValue().set("num", String.valueOf(num));
        }

    }

}
